package net.com.hydroPK.net.codec;

import net.com.hydroPK.Engine;
import net.com.hydroPK.Server;
import net.com.hydroPK.model.Player;
import net.com.hydroPK.util.Misc;

/**
 * RS2LoginProtocol class - Handles logins
 */

public class RS2LoginProtocol {
    /**
     * Validate a connection.
     * <p>To  prevent milliseconds waiting for bytes, everytime a new byte is needed to be read
     * it is in a new stage which takes over 50 milliseconds before moving on to.
     * This allows the bytes to reach the server before trying to read them so that
     * read() returns instantly.
     *
     * @param p The Player which the frame should be created for.
     */
    public void login(Player p) {
        if (p == null || p.stream == null) {
            return;
        }

        long serverSessionKey = ((long) (Math.random() * 99999999D) << 32) + (long) (Math.random() * 99999999D);
        long clientSessionKey = 0;
        int returnCode = 2;
        if (p.loginStage < -1) {
            updateServer(p);
        } else if (p.loginStage == 0) {
            try {
                if (!fillStream(p, 2))
                    return;
            } catch (Exception e) {
                return;
            }
            int connectionType = p.stream.readUnsignedByte();
            if (connectionType == 15) {
                updateServer(p);
                p.loginStage = -5;
                return;
            }
            if (connectionType == 23) {
                data(p);
                p.loginStage = -3;
                return;
            }
            if (connectionType != 14) {
                p.loginStage = -1;
                return;
            }
            int longPlayerName = p.stream.readUnsignedByte();
            p.stream.writeByte(0);
            p.stream.writeQWord(serverSessionKey);
            directFlushStream(p);
            p.loginStage++;
        } else if (p.loginStage == 1) {
            try {
                if (!fillStream(p, 3))
                    return;
            } catch (Exception e) {
                return;
            }
            int loginType = p.stream.readUnsignedByte();
            if (loginType != 16 && loginType != 18 && loginType != 14) {
                p.loginStage = -1;
                return;
            }
            p.loginStage++;
        } else if (p.loginStage == 2) {
            int loginPacketSize = p.stream.readUnsignedWord();
            int loginEncryptPacketSize = loginPacketSize - (36 + 1 + 1 + 2);
            if (loginEncryptPacketSize <= 0) {
                p.loginStage = -1;
                return;
            }
            try {
                if (!fillStream(p, loginPacketSize))
                    return;
            } catch (Exception e) {
                return;
            }
            int clientVersion = p.stream.readDWord();
            if (clientVersion != 508)
                if (clientVersion != 523) {
                    p.loginStage = -1;
                    return;
                }
            p.stream.readUnsignedByte();
            p.stream.readUnsignedWord();
            p.stream.readUnsignedWord();
            p.stream.readUnsignedWord(); //client height too
            for (int i = 0; i < 24; i++) {
                int cacheIDX = p.stream.readUnsignedByte();
                if (cacheIDX == 0) {
                    p.loginStage = -1;
                    return;
                }
            }
            String junk = p.stream.readString();
            for (int i = 0; i < 29; i++) {
                int junk2 = p.stream.readDWord();
            }
            loginEncryptPacketSize--;
            int junk29 = p.stream.readUnsignedByte();
            int encryption = junk29;
            if (!(encryption == 10 || encryption == 64)) {
                encryption = p.stream.readUnsignedByte();
            }
            if (encryption != 10 && encryption != 64) {
                p.loginStage = -1;
                return;
            }

            clientSessionKey = p.stream.readQWord();
            serverSessionKey = p.stream.readQWord();
            String user = Misc.longToString(p.stream.readQWord()).toLowerCase().replaceAll(" ", "_");
            //System.out.println("User incoming: "+user);
            user = user.replaceAll("_", " ");
            user = user.replaceAll("<", "");
            user = user.replaceAll(">", "");
            p.username = user.trim();
            if (p.username == null) {
                p.loginStage = -1;
                p.username = "";
                return;
            }
            char first = p.username.charAt(0);
            p.username2 = Character.toUpperCase(first) + p.username.substring(1, p.username.length());
            for (int i = 0; i < p.username.length(); i++) {
                Character c = new Character(p.username.charAt(i));
                if (!Character.isLetterOrDigit(c) && !Character.isSpaceChar(c)) {
                    p.loginStage = -1;
                    p.username = "";
                    return;
                }
            }
            if (playerOnline(p.username, p)) {
                returnCode = 5;
            }
            Server.loadBannedUsers();
            if (checkBannedUsers(p.username)) {
                returnCode = 4;
            }
            String password = p.stream.readString();
            if (password == null) {
                p.loginStage = -1;
                return;
            }
            for (int i = 0; i < password.length(); i++) {
                Character c = password.charAt(i);
                if (!Character.isLetterOrDigit(c) && !Character.isSpaceChar(c)) {
                    p.loginStage = -1;
                    return;
                }
            }
            p.newPassword = password;
            p.loadCharacter();
            if (password != null && p.password != null && !p.password.equals("") && !p.password.equals(password)) {
                returnCode = 3;
            } else {
                p.password = password;
            }

            int ipconnections = 0;
            for (int i = 0; i < Engine.players.length; i++) {
                Player PC = Engine.players[i];
                if (PC != null) {
                    if (PC.socket.socket.getInetAddress().getHostAddress().equals(
                            p.socket.socket.getInetAddress().getHostAddress())) {
                        // MATCHED IP
                        ipconnections += 1;
                    }
                }
            }
            if (ipconnections >= 3) {
                // More than 2 connections
                returnCode = 9;
            }

            p.stream.writeByte(returnCode);
            p.stream.writeByte(p.rights);
            p.stream.writeByte(0);
            p.stream.writeByte(0);
            p.stream.writeByte(0);
            p.stream.writeByte(1);
            p.stream.writeByte(0);
            p.stream.writeByte(p.playerId);
            p.stream.writeByte(0);
            directFlushStream(p);
            //boolean found = false;
            if (returnCode != 2) {
                p.appendData("characters/ips/" + p.username + ".txt", "[" + Server.socketListener.
                        getAddress(p.socket.socket) + "]: failed login.");
            } else {
                Engine.playerMovement.getNextPlayerMovement(p);
                if (p.inJadCave()) {
                    if (p.heightLevel > 0)
                        p.heightLevel = 0;
                }
                p.getActionSender().setMapRegion(p);
                p.getActionSender().setWindowPane(p, 548);
                directFlushStream(p);

                p.appendData("characters/ips/" + p.username + ".txt", "[" + Server.socketListener.
                        getAddress(p.socket.socket) + "]: successful login.");
                p.getActionSender().setInterfaces(p);
                p.getActionSender().setConfig(p, 1249, p.bankX);
                for (int i = 0; i < p.skillLvl.length; i++) {
                    p.getActionSender().setSkillLvl(p, i);
                }
                p.getStartup().startupFeatures();
                p.runEnergyUpdateReq = true;
            }
        }
    }

    /**
     * If the connection is the client's update server than send the keys.
     *
     * @param p The Player which the frame should be created for.
     */
    private void updateServer(Player p) {
        if (p == null) {
            return;
        }
        try {
            if (p.loginStage == 0) {
                if (!fillStream(p, 3))
                    return;
                p.stream.writeByte(0);
                directFlushStream(p);
            } else if (p.loginStage == -5) {
                if (!fillStream(p, 8))
                    return;
                for (int i = 0; i < Misc.uKeys.length; i++) {
                    p.stream.writeByte(Misc.uKeys[i]);
                }
                directFlushStream(p);
                p.loginStage = -1;
            }
        } catch (Exception exception) {
        }
    }

    private void data(Player p) {
        if (p == null) {
            return;
        }
        try {
            if (p.loginStage == -3) {
                if (!fillStream(p, 3))
                    return;
                p.stream.writeByte(1);
                directFlushStream(p);
            } else if (p.loginStage == -3) {
                if (!fillStream(p, 8))
                    return;
                for (int i = 0; i < Misc.data.length; i++) {
                    p.stream.writeByte(Misc.data[i]);
                }
                directFlushStream(p);
                p.loginStage = -1;
            }
        } catch (Exception exception) {
        }
    }

    /**
     * Make sure the player isn't already online.
     *
     * @param name The name to compare with.
     * @param _p   The Player which the frame should be created for.
     */
    private boolean playerOnline(String name, Player _p) {
        for (Player p : Engine.players) {
            if (p != null && _p.playerId != p.playerId) {
                if (p.username.equalsIgnoreCase(name)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Checks if a user is banned.
     *
     * @param username The name to check.
     * @return Returns if the name was found.
     */
    public boolean checkBannedUsers(String username) {
        if (username == null) {
            return true;
        }
        for (int i = 0; i < Server.bannedUsers.length; i++) {
            if (Server.bannedUsers[i] != null && username.equalsIgnoreCase(Server.bannedUsers[i])) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check and read any incoming bytes.
     *
     * @param p         The Player which the frame should be created for.
     * @param forceRead How many bytes to read from the buffer.
     */
    private boolean fillStream(Player p, int forceRead) throws Exception {
        if (p == null) {
            return false;
        }
        if (forceRead >= 500) {
            return false;
        }
        if (p.socket.avail() < forceRead) {
            return false;
        }
        p.stream.inOffset = 0;
        p.socket.read(forceRead);
        return true;
    }

    /**
     * Send the bytes in the stream's outBuffer directly to the client.
     *
     * @param p The Player which the frame should be created for.
     */
    private void directFlushStream(Player p) {
        if (p == null) {
            return;
        }
        try {
            p.socket.write(p.stream.outBuffer, 0, p.stream.outOffset);
            p.stream.outOffset = 0;
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }
}